﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Borg.Properties;

namespace Borg
{
    public class SettingDictionary : SettingCollection, IEnumerable<KeyValuePair<string, object>>
    {
        protected readonly OrderedDictionary<string, object> settings =
            new OrderedDictionary<string, object>();

        public object this[string key]
        {
            get
            {
                if (key == null)
                    throw new ArgumentNullException("key");

                if (string.IsNullOrEmpty(key))
                    throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");

                if (!settings.ContainsKey(key))
                    throw new ArgumentException(Exceptions.Key_is_not_in_setting_dictionary, "key");
                
                return InternalGet(key);
            }
            set
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                
                if (string.IsNullOrEmpty(key))
                    throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
                
                if (SettingStore != null && !SettingStore.CheckAccess())
                    throw RuntimeElement.GetAccessException();
                
                if (value is SettingCollection)
                    InternalSet(key, value);
                else
                    InternalSetValue(key, value);
            }
        }

        public int Count
        {
            get { return settings.Count; }
        }

        public ICollection<string> Keys
        {
            get { return settings.Keys; }
        }

        public ICollection<object> Values
        {
            get { return settings.Values; }
        }

        public T GetValue<T>(string key, T defaultValue)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            return InternalGetValue(key, defaultValue);
        }

        public T GetValue<T>(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (!settings.ContainsKey(key))
                throw new ArgumentException(Exceptions.Key_is_not_in_setting_dictionary, "key");
            
            return InternalGetValue<T>(key);
        }

        public SettingDictionary GetDictionary(string key, SettingDictionary defaultValue)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            return InternalGetCollection(key, defaultValue);
        }

        public SettingDictionary GetDictionary(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (!settings.ContainsKey(key))
                throw new ArgumentException(Exceptions.Key_is_not_in_setting_dictionary, "key");
            
            return InternalGetCollection<SettingDictionary>(key);
        }

        public SettingList GetList(string key, SettingList defaultValue)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            return InternalGetCollection(key, defaultValue);
        }

        public SettingList GetList(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            return InternalGetCollection<SettingList>(key);
        }

        public void SetValue<T>(string key, T value)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (SettingStore != null && !SettingStore.CheckAccess())
                throw RuntimeElement.GetAccessException();
            
            InternalSetValue(key, value);
        }

        public void SetDictionary(string key, SettingDictionary settingDictionary)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (settingDictionary == null)
                throw new ArgumentNullException("settingDictionary");
            
            if (settingDictionary.Parent != null)
                throw new ArgumentException(Exceptions.Setting_collection_already_added);
            
            if (SettingStore != null && !SettingStore.CheckAccess())
                throw RuntimeElement.GetAccessException();
            
            InternalSet(key, settingDictionary);
        }

        public void SetList(string key, SettingList settingList)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (settingList == null)
                throw new ArgumentNullException("settingList");
            
            if (settingList.Parent != null && settingList.Parent != this)
                throw new ArgumentException(Exceptions.Setting_collection_already_added);
            
            if (SettingStore != null && !SettingStore.CheckAccess())
                throw RuntimeElement.GetAccessException();
            
            InternalSet(key, settingList);
        }

        public bool Remove(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (SettingStore != null && !SettingStore.CheckAccess())
                throw RuntimeElement.GetAccessException();
            
            return InternalRemove(key);
        }

        public bool Contains(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            return InternalContains(key);
        }

        public void Clear()
        {
            if (SettingStore != null && !SettingStore.CheckAccess())
                throw RuntimeElement.GetAccessException();
            
            InternalClear();
        }

        public KeyValuePair<string, object>[] ToArray()
        {
            var array = new KeyValuePair<string, object>[settings.Count];
            
            settings.CopyTo(array, 0);
            
            return array;
        }

        public void CopyTo(SettingDictionary settingDictionary)
        {
            var dest = new string[settingDictionary.Keys.Count];
            settingDictionary.Keys.CopyTo(dest, 0);

            foreach (var key in dest.Where(key => !InternalContains(key)))
                settingDictionary.InternalRemove(key);

            foreach (var pair in this)
            {
                var key = pair.Key;
                var val = pair.Value;
                
                if (val is SettingCollection)
                    settingDictionary.InternalSet(key, val);
                else
                    settingDictionary.InternalSetValue(key, val);
            }
        }

        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return settings.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        protected internal virtual object InternalGet(string key)
        {
            return settings[key];
        }

        protected internal virtual T InternalGetValue<T>(string key, T defaultValue)
        {
            return
                settings.ContainsKey(key)
                    ? InternalGetValue<T>(key)
                    : InternalSetValue(key, defaultValue);
        }

        protected internal virtual T InternalGetValue<T>(string key)
        {
            return ConvertFromString<T>(settings[key].ToString());
        }

        protected internal virtual T InternalGetCollection<T>(string key, T defaultValue)
            where T : SettingCollection
        {
            if (settings.ContainsKey(key))
                return InternalGetCollection<T>(key);

            InternalSet(key, defaultValue);
            
            return defaultValue;
        }

        protected internal virtual T InternalGetCollection<T>(string key)
            where T : SettingCollection
        {
            return (T)settings[key];
        }

        protected internal virtual T InternalSetValue<T>(string key, T value)
        {
            InternalSet(key, ConvertToString(value));
            return value;
        }

        protected internal virtual void InternalSet(string key, object value)
        {
            if (settings.ContainsKey(key))
            {
                var set = settings[key];

                if (Equals(set, value))
                    return;

                var settingCollection = set as SettingCollection;

                if (settingCollection != null)
                    DeregisterCollection(settingCollection);
            }
            
            settings[key] = value;

            var collection = value as SettingCollection;

            if (collection != null)
                RegisterCollection(collection);
            
            OnCollectionChanged();
        }

        protected internal virtual bool InternalRemove(string key)
        {
            if (!settings.ContainsKey(key))
                return false;

            var set = settings[key];

            var settingCollection = set as SettingCollection;

            if (settingCollection != null)
                DeregisterCollection(settingCollection);
            
            settings.Remove(key);
            OnCollectionChanged();
            
            return true;
        }

        protected internal virtual bool InternalContains(string key)
        {
            return settings.ContainsKey(key);
        }

        protected internal virtual void InternalClear()
        {
            if (settings.Count == 0)
                return;

            foreach (var set in settings.Values.OfType<SettingCollection>())
                DeregisterCollection(set);

            settings.Clear();
            OnCollectionChanged();
        }

        private void RegisterCollection(SettingCollection collection)
        {
            collection.Parent = this;
            collection.CollectionChanged += ChildCollectionChanged;
        }

        private void DeregisterCollection(SettingCollection collection)
        {
            collection.Parent = null;
            collection.CollectionChanged -= ChildCollectionChanged;
        }

        private void ChildCollectionChanged(object sender, EventArgs e)
        {
            OnCollectionChanged();
        }
    }
}
